'''This module defines actor and component classes. It also includes functions,
enums, and decorators needed for other classes to implement networking support.
'''
from __future__ import print_function
from __future__ import unicode_literals
from __future__ import division

import collections
import logging
import cPickle as cpickle

from kharlia.core import NativeClass, LoadConfig, CObject, CWeakRefDescr
from kharlia.engine.base import GetEngine, FRectF, FVector3F
from kharlia.engine.client import EKey, FTexture, CSpriteAniDef

from kharlia._engine import (
    EDirection, ENetRole, ENetMode, UNASSIGNED_NETWORK_ID,
    CActorBase, AComponentBase, ATransformBase, AMovementBase, AColliderBase,
    ARenderBase, ASpriteBase, FSpriteFrame, ASpriteAnimationBase, AColliderBase,
    ACameraBase, AControllerBase, APlayerControllerBase, APawnBase, ANetworkBase
    )

__all__ = [
    'UNASSIGNED_NETWORK_ID', # constants
    'EDirection',
    'ENetRole',
    'ENetMode',
    'GetComponentClasses', # functions
    'InitComponentClasses',
    'GetFullVarInfoMap',
    'Simulated', # decorators
    'ClientMethod',
    'ServerMethod',
    'ComponentClass',
    'CActor', # classes
    'AComponent',
    'ATransform',
    'AMovement',
    'ARender',
    'ASprite',
    'ACollider',
    'ACamera',
    'ANetwork',
    'AController',
    'APlayerController',
    'APawn',
    'AGameInfo',
    'ALevelLink',
    ]

GLogger = logging.getLogger('engine')
# replication specific logger
GRepLogger = logging.getLogger('engine.rep')
# Ordered dict of AComponent classes keyed by name
GComponentClasses = collections.OrderedDict()
# List of classes with netvars
GNetEnabledClasses = []

def GetComponentClasses():
    return GComponentClasses

def InitComponentClasses():
    '''
    This function sets up certain variables for component classes needed to
    implement some other features.

    Class variables set by this function:
      _ClassNetVars - Python net variables in this class
      _AllNetVars - Python net variable of this and all base classes
      _RepListClassIndices - Indices of classes in the MRO that define GetRepList()
    '''
    GLogger.debug('initing actor classes...')
    _VerifyComponentClasses()
    counter = 1
    for cls in GComponentClasses.itervalues():
        clsdict = cls.__dict__
        if clsdict.get('__initedcompcls__', False):
            continue
        GLogger.debug('initing component class %s', cls.__name__)
        # Build net var list
        clsnetvars = []
        clsnotifies = set()
        allnetvars = []
        allnotifies = set()

        netvars, notifies = _GetRepVars(clsdict.get('__varinfo__', ()))
        if netvars is not None and len(netvars) > 0:
            clsnetvars.extend(netvars)
            clsnotifies.update(notifies)
            cls.__repindex__ = counter
            counter += 1

        # We'll extend allnetvars with this in the MRO loop
        cls.__classrepvars__ = tuple(clsnetvars)
        cls.__classrepnotifies__ = frozenset(clsnotifies)

        # Build list of all net vars from super classes
        for i in reversed(cls.mro()):
            id = i.__dict__
            v = id.get('__classrepvars__', None)
            n = id.get('__classrepnotifies__', None)
            if v is not None:
                allnetvars.extend(v)
                allnotifies.update(n)

        if len(clsnetvars) > 0:
            assert clsnetvars[-1] == allnetvars[-1]

        # List of net vars for this class and its bases
        cls.__allrepvars__ = tuple(allnetvars)
        cls.__allrepnotifies__ = frozenset(allnotifies)

        # Check for classes that have GetRepList defined
        grlclasses = []
        for i, c in enumerate(cls.mro()):
            if '__getreplist__' in c.__dict__:
                grlclasses.append(i)
        cls.__repclassindices__ = tuple(grlclasses)
        cls.__initedcompcls__ = True
    
    GLogger.debug('finished initing component classes')

def _GetRepVars(vars):
    names = []
    notifynames = set()
    for var in vars:
        rep = var.get('rep', False)
        if rep:
            names.append(var['name'])
            if var.get('repnotify', False):
                notifynames.add(var['name'])
    return names, notifynames

def _VerifyComponentClasses():
    for cls in GComponentClasses.itervalues():
        if not issubclass(cls, AComponent):
            raise TypeError('registered component class \'{0}\' is ' \
                            'not a subclass of AComponent'.format(i))

def GetFullVarInfoMap(ctype):
    assert issubclass(ctype, AComponent)
    mro = ctype.mro()
    fullvarinfo = collections.OrderedDict()
    for cls in reversed(mro):
        varinfo = cls.__dict__.get('__varinfo__', ())
        for vi in varinfo:
            fullvarinfo[vi['name']] = vi
    return fullvarinfo

def Simulated(func):
    func.Simulated = True
    return func

def ClientMethod(func):
    funcname = func.__name__
    
    @Simulated
    def _ClientMethodWrapper(self, *args):
        world = self.Level.World
        if world.NetIsServer:
            world.NetSendActorRPC(False, self, funcname, *args)
        else:
            return func(self, *args)
   
    return _ClientMethodWrapper

def ServerMethod(func):
    funcname = func.__name__
    
    @Simulated
    def _ServerMethodWrapper(self, *args):
        world = self.Level.World
        if world.NetIsClient:
            world.NetSendActorRPC(False, self, funcname, *args)
        else:
            return func(self, *args)
   
    return _ServerMethodWrapper

def ComponentClass(cls):
    assert issubclass(cls, AComponent)
    name = cls.__name__
    if name in GComponentClasses:
        raise RuntimeError('component class named "{0}" already registered'.format(name))
    GComponentClasses[name] = cls
    return cls 

@NativeClass
class AComponent(AComponentBase, CObject):
    PlaceableClass = False
    __varinfo__ = ()
    
    def __init__(self):
        self.__initnative__()
        CObject.__init__(self)

    def __reduce_ex__(self, protocol):
        assert protocol == 2
        return (type(self), (), self.__getstate__())
    
    def __getstate__(self):
        VERSION = 2
        return (
            VERSION,
            self._Actor,
            self._Level,
            self._NoDelete
            )
    
    def __setstate__(self, state):
        version = state[0]
        self._Actor = state[1]
        self._Level = state[2]
        self._NoDelete = state[3]
    
    def __repr__(self):
        if self.Actor:
            return '<comp {} of actor {}>'.format(self.__class__.__name__,
                                                  self.Actor.IDStr)
        else:
            return '<comp {}>'.format(self.__class__.__name__)

    def OnGameStarted(self):
        GLogger.debug('game started for: %s', self)

    def OnGameStopped(self):
        GLogger.debug('game stopped for: %s', self)
    
@NativeClass
@ComponentClass
class ATransform(ATransformBase, AComponent):    
    PlaceableClass = True
    __varinfo__ = (
        dict(name='Position', type=FVector3F, default=FVector3F()),
        )
    
    def __init__(self):
        self.__initnative__()
        AComponent.__init__(self)
        
    def __getstate__(self):
        VERSION = 1
        return (
            VERSION,
            AComponent.__getstate__(self),
            self.Position
            )
    
    def __setstate__(self, state):
        #version = state[0]
        AComponent.__setstate__(self, state[1])
        self.Position = state[2]
        
    def __getreplist__(self, role, remoterole):
        if role == ROLE_AUTHORITY:
            yield 'Position',
        

@NativeClass
@ComponentClass
class AMovement(AMovementBase, AComponent):
    PlaceableClass = True
    __varinfo__ = (
        dict(name='Velocity', type=FVector3F, definit=True),
        dict(name='Acceleration', type=FVector3F, definit=True),
        dict(name='MaxVelocity', type=FVector3F, definit=True),
        dict(name='MaxAcceleration', type=FVector3F, definit=True),
        )

    def __init__(self):
        self.__initnative__()
        AComponent.__init__(self)
        
    def __getstate__(self):
        VERSION = 2
        state = (
            VERSION,
            super(AMovement, self).__getstate__(),
            self.Velocity,
            self.Acceleration,
            self.MaxVelocity,
            self.MaxAcceleration
            )
        return state
    
    def __setstate__(self, state):
        version = state[0]
        super(AMovement, self).__setstate__(state[1])
        self.Velocity = state[2]
        self.Acceleration = state[3]
        self.MaxVelocity = state[4]
        self.MaxAcceleration = state[5]
        # state[6] is transform in version 1
        
    def __getreplist__(self, role, remoterole):
        if role == ROLE_AUTHORITY:
            yield 'Velocity', 'Acceleration', 'MaxVelocity', 'MaxAcceleration'
    
@NativeClass
@ComponentClass
class ARender(ARenderBase, AComponent):
    def __init__(self):
        if type(self) is ARender:
            raise RuntimeError('abstract')
        self.__initnative__()
        AComponent.__init__(self)
        
    def __getstate__(self):
        VERSION = 1
        state = (
            VERSION,
            AComponent.__getstate__(self),
            self.Hidden
        )
        return state
    
    def __setstate__(self, state):
        verison = state[0]
        AComponent.__setstate__(self, state[1])
        self.Hidden = state[2]
        
    def __getreplist__(self, role, remoterole):
        if role == ROLE_AUTHORITY:
            yield 'Hidden',

@NativeClass
@ComponentClass
class ASprite(ASpriteBase, ARender):
    PlaceableClass = True
    __varinfo__ = (
        dict(name='Image', type=FTexture, default=None),
        dict(name='Direction', type=EDirection, default=EDirection.NORTH),
        )
    
    def __init__(self):
        self.__initnative__()
        ARender.__init__(self)
        
    def __getstate__(self):
        VERSION = 1
        state = (
            VERSION,
            ARender.__getstate__(self),
            self.Image,
            self.Direction
            )
        return state

    def __setstate__(self, state):
        version = state[0]
        ARender.__setstate__(self, state[1])
        self.Image = state[2]
        self.Direction = state[3]
        
    def __getreplist__(self, role, remoterole):
        if role == ROLE_AUTHORITY:
            yield 'Image', 'Direction'
    
def SortedListFromDict(d):
    items = map(lambda x: (int(x[0]), x[1]), d.iteritems())
    items.sort(lambda x, y: cmp(x[0], y[0]))
    items = map(lambda x: x[1], items)
    return items

@NativeClass
@ComponentClass
class ASpriteAnimation(ASpriteAnimationBase, ASprite):
    __varinfo__ = (
        dict(name='Definition', type=CSpriteAniDef, default=None),
        dict(name='CurrentFrameList', type=int, default=0),
        dict(name='CurrentFrame', type=int, default=0),
        dict(name='ShouldMapToDirections', type=bool, default=True)
        )

    def __init__(self):
        self.__initnative__()
        ASprite.__init__(self)

    def __getstate__(self):
        VERSION = 1
        return (
            VERSION,
            ASprite.__getstate__(self),
            self.Definition,
            self.CurrentFrameList,
            self.CurrentFrame,
            self.ShouldMapToDirections
            )

    def __setstate__(self, state):
        version = state[0]
        ASprite.__setstate__(self, state[1])
        self.Definition = state[2]
        self.CurrentFrameList = state[3]
        self.CurrentFrame = state[4]
        self.ShouldMapToDirections = state[5]

@NativeClass
@ComponentClass
class ACollider(AColliderBase, AComponent):
    PlaceableClass = True

    __varinfo__ = (
        dict(name='Bounds', type=FRectF, default=FRectF(), rep=True),
        dict(name='CollideActors', type=bool, default=False, rep=True),
        dict(name='CollideTiles', type=bool, default=False, rep=True),
        dict(name='BlockActors', type=bool, default=False, rep=True),
        dict(name='CollisionLayer', type=int, default=0, rep=True)
        )

    def __init__(self):
        self.__initnative__()
        AComponent.__init__(self)
        self.LastBumpCollider = None
        self.LastBumpTime = None
        
    def __getstate__(self):
        VERSION = 3
        # 3 - Add BlockActors, touchers
        state = (
            VERSION,
            super(ACollider, self).__getstate__(),
            self.Bounds,
            self.CollideActors,
            self.CollideTiles,
            self.CollisionLayer,
            self.BlockActors,
            list(self.IterTouchers())
        )
        return state
    
    def __setstate__(self, state):
        version = state[0]
        super(ACollider, self).__setstate__(state[1])
        self.Bounds = state[2]
        self.CollideActors = state[3]
        self.CollideTiles = state[4]
        self.CollisionLayer = state[5]
        if version >= 3:
            self.BlockActors = state[6]
            for t in state[7]:
                self._AddToucher(t)
        
    def __getreplist__(self, role, remoterole):
        if role == ROLE_AUTHORITY:
            yield 'Bounds', 'CollideActors', 'CollideTiles', 'CollisionLayer'
        
    def _TouchingActorsCallback(self, wr):
        self.TouchingActors.remove(wr)

    def BeginTouch(self, other):
        '''Called by CLevel.MoveActor() on collision.'''
        super(ACollider, self).BeginTouch(other)
        GLogger.debug('%s begin touching by %s', self, other)
        self.Actor.SendMessage('BeganTouch', other)

    def EndTouch(self, other):
        super(ACollider, self).EndTouch(other)
        GLogger.debug('%s end touching by %s', self, other)
        self.Actor.SendMessage('EndedTouch', other)

    def IterTouchers(self):
        for i in xrange(self.ToucherCount):
            yield self.GetToucher(i)

    def Bump(self, other):
        curtime = self.Level.Game.RealTime
        if (other != self.LastBumpCollider or
                self.LastBumpTime <= curtime - 1.0):
            GLogger.debug('%s bumped by %s', self, other)
            self.Actor.SendMessage('Bumped', other)
            self.LastBumpCollider = other
            self.LastBumpTime = curtime

    def OnChangingLevel(self, destlevel, destpos):
        self.EndTouches()
    
APhysics = ACollider # Temporary redirect for unpickling

@NativeClass
@ComponentClass
class ACamera(ACameraBase, AComponent):
    PlaceableClass = True
    def __init__(self):
        self.__initnative__()
        AComponent.__init__(self)
        
    def __getstate__(self):
        VERSION = 1
        state = (
            VERSION,
            super(ACamera, self).__getstate__(),
            self.Target,
            self.TargetOffset
            )
        return state
    
    def __setstate__(self, state):
        version = state[0]
        super(ACamera, self).__setstate__(state[1])
        self.Target = state[2]
        self.TargetOffset = state[3]
        
    def __getreplist__(self, role, remoterole):
        if role == ROLE_AUTHORITY:
            yield 'Target', 'TargetOffset'
    
@NativeClass
class AController(AControllerBase, AComponent):
    __varinfo__ = (
        dict(name='Pawn', type=AComponent, default=None),
        )

    def __init__(self):
        self.__initnative__()
        AComponent.__init__(self)
        
    def __getstate__(self):
        VERSION = 1
        return (
            VERSION,
            super(AController, self).__getstate__(),
            self._Pawn
            )
    
    def __setstate__(self, state):
        version = state[0]
        super(AController, self).__setstate__(state[1])
        self._Pawn = state[2]
        
    #def __getreplist__(self, role, remoterole):
    #    if role == ROLE_AUTHORITY:
    #        yield 'Pawn', 
    
    @property
    def PawnMovement(self):
        '''
        Get the movement component of the pawn, or None if no pawn or
        the pawn has no movement component.
        '''
        if self._Pawn:
            return self._Pawn.Actor.Movement
                
    def OnGameStarted(self):
        AComponent.OnGameStarted(self)
        
    def OnGameStopped(self):
        p = self._Pawn
        self.Unpossess()
        if p is not None:
            print('controller destroying pawn:', p)
            p.Level.DestroyActor(p.Actor)
        AComponent.OnGameStopped(self)
            
    def Possess(self, pawn):
        if not isinstance(pawn, APawn):
            raise TypeError('not APawn')
        if self.Pawn:
            self.Unpossess()
        pawn.PossessedBy(self)
        self._Pawn = pawn
        
    def Unpossess(self):
        if self._Pawn is not None:
            self._Pawn.Unpossessed()
            self._Pawn = None

    def PawnApplyDamage(self, amount, type, instigator):
        '''Make any adjustments to damage being applied to pawn.
        
        Returns (ignore:bool, damage:float).
        '''
        return False, amount

    def PawnKill(self, instigator):
        return False

    def PawnKilled(self, instigator):
        pass

    def PawnTouchedLevelLink(self, link):
        '''Notification that this controller's pawn has touched a level link and
        is going to change levels. Return True to cancel the change.
        '''
        return False

@NativeClass
@ComponentClass
class APlayerController(APlayerControllerBase, AController):
    PlaceableClass = True
    __varinfo__ = (
        dict(name='Camera', type=AComponent, default=None),
        dict(name='TestVar', type=str, default=None, rep=True, repnotify=True),
        dict(name='GodMode', type=bool, default=False, rep=True),
        dict(name='HealthRegenRate', type=float, default=5.0, rep=True),
        )
    # Set of keys that will have states tracked by the game
    TrackKeys = {
        EKey.ARROW_LEFT,
        EKey.ARROW_RIGHT,
        EKey.ARROW_UP,
        EKey.ARROW_DOWN
        }

    def __init__(self):
        self.__initnative__()
        AController.__init__(self)
        self.TestVar = None
        self.GodMode = False # Damage to pawn ignored if true
        self.HealthRegenRate = 5.0 # Health regenerated per second
        self.HUD = None # The player's HUD, if this controller is for a local player
        self.ResetInput()
        
    def __getstate__(self):
        VERSION = 2
        # 2 - add GodMode, HealthRegenRate
        print('GETTING PC STATE')
        return (
            VERSION,
            super(APlayerController, self).__getstate__(),
            self.GodMode,
            self.Camera,
            self.HealthRegenRate,
            )
    
    def __setstate__(self, state):
        print('LOADING PC STATE')
        version = state[0]
        super(APlayerController, self).__setstate__(state[1])
        self.Camera = state[3]
        if version >= 2:
            self.GodMode = state[2]
            self.HealthRegenRate = state[4]
        
    def __getreplist__(self, role, remoterole):
        if role == ROLE_AUTHORITY:
            yield 'TestVar', 'GodMode', 'HealthRegenRate'
                
    def Init(self, owner):
        super(APlayerController, self).Init(owner)
        
    def Exit(self):
        assert not self.HUD
        super(APlayerController, self).Exit()
        
    def OnGameStarted(self):
        super(APlayerController, self).OnGameStarted()
        
    def OnGameStopped(self):
        # TODO: Find out why exception here causes negative refcount error with GC
        self.CleanupHUD()
        self.ResetInput()
        assert self.Level.Game
        self.Level.Game.Logout(self)
        super(APlayerController, self).OnGameStopped()

    def Tick(self, delta):
        super(APlayerController, self).Tick(delta)

        # Initialize the HUD if we have a local player
        if not self.HUD and self.Player and self.Player.IsLocal:
            cl = self.Level.World.Engine.Client
            if cl and cl.InitedUI:
                self.InitHUD(cl)

        # Apply health regeneration to pawn if its not dead
        pawn = self.Pawn
        if pawn and pawn.Health > 0.0 and pawn.Health < pawn.MaxHealth:
            rate = max(self.HealthRegenRate, 0.0)
            pawn.Health += rate * delta

    def OnFieldReplicated(self, name):
        if name == 'GodMode':
            GLogger.info('GodMode replicated for %s', self)

    #-- HUD

    def InitHUD(self, client):
        '''Initialize the player's HUD.'''
        if self.HUD:
            return
        assert client.InitedUI
        if client.IsWindowPresent(b'PCHUD'):
            GLogger.error('could not initialize HUD, window name taken')
            return
        root = client.GetWindow(b'root')
        win = client.CreateWindow(b'Vanilla/StaticText', b'PCHUD')
        win.Position = ((0.0125, 0.0), (0.0, 48.0))
        win.Size = ((1.0, 0.0), (0.0, 32.0))
        win.SetProperty(b'FrameEnabled', b'false')
        win.SetProperty(b'BackgroundEnabled', b'false')
        root.AddChildWindow(win)
        client.EventUIExiting.AddWeakAttr(self, 'OnEventClientUIExiting')
        client.EventPreRender.AddWeakAttr(self, 'OnEventClientPreRender')
        self.HUD = win

    def CleanupHUD(self):
        if not self.HUD:
            return
        client = GetEngine().Client
        if client:
            client.DestroyWindow(self.HUD)
        self.HUD = None

    def OnEventClientUIExiting(self, sender):
        '''Clear any references to the soon-to-be invalid CUIWindow instances.'''
        self.HUD = None

    def OnEventClientPreRender(self, sender):
        '''Update the HUD text before each render.'''
        if self.HUD and self.Pawn:
            self.HUD.Text = b'HP: {0} / {1}'.format(self.Pawn.Health, self.Pawn.MaxHealth)

    #-- Command execution

    def Exec(self, cmd, out):
        if cmd[0] == 'resetinput':
            self.ResetInput()
            out('Reset PC input and velocity')
            return True

        # Apply damage to ourself
        if cmd[0] == 'applydamage':
            if len(cmd) < 2:
                out('Need damage value.')
                return True
            if not self.Pawn:
                out('No pawn.')
                return True
            try:
                amount = float(cmd[1])
            except ValueError:
                out('Invalid float value.')
                return True

            amount = max(amount, 0.0)
            self.Pawn.ApplyDamage(amount, type=None, instigator=None)
            out('Applying damage: {0}'.format(amount))
            out('Pawn health: {0}/{1}'.format(self.Pawn.Health, self.Pawn.MaxHealth))
            return True

        if cmd[0] == 'possess':
            if len(cmd) < 2:
                out('Need pawn actor ID.')
                return True
            # Try finding by name
            id = unicode(cmd[1])
            for level in self.Level.World.IterLevels():
                actor = level.GetActorByName(id)
                if actor:
                    break
            if not actor:
                # Try finding by ID
                try:
                    id = int(id)
                except ValueError:
                    out('Invalid actor ID number.')
                    return True
            for level in self.Level.World.IterLevels():
                actor = level.GetActorByID(id)
                if actor:
                    break
            if not actor:
                out('Actor not found.')
                return True
            if not actor.Pawn:
                out('Actor cannot be possessed.')
                return True
            if actor.Pawn.Controller:
                out('Actor already possessed.')
                return True
            self.Possess(actor.Pawn)
            out('Possessed actor {}'.format(self.Pawn))
            return True

        if cmd[0] == 'unpossess':
            if self.Pawn:
                out('Unpossessing pawn {0}'.format(self.Pawn))
                self.Unpossess()
            else:
                out('No pawn to unpossess.')
            return True

        return False

    #-- Pawn handling

    def PawnApplyDamage(self, amount, type, instigator):
        if self.GodMode:
            # Pawn ignores damage
            return True, amount
        return super(APlayerController, self).PawnApplyDamage(amount, type, instigator)

    #-- Input handling

    def InputKeyDown(self, keycode):
        prev = self.KeyStates.get(keycode, False)
        
        if keycode in self.TrackKeys:
            self.KeyStates[keycode] = True
            
        m = self.Pawn.Actor.Movement if self.Pawn else None
        if not prev and m:
            if keycode == EKey.ARROW_LEFT:
                m.Velocity.X -= 8.0
            elif keycode == EKey.ARROW_RIGHT:
                m.Velocity.X += 8.0
            elif keycode == EKey.ARROW_DOWN:
                m.Velocity.Y += 8.0
            elif keycode == EKey.ARROW_UP:
                m.Velocity.Y -= 8.0
        # limit the vector magnitude
        mlen = m.Velocity.Length
        if mlen > 8.0:
            mnorm = m.Velocity / mlen
            m.Velocity = mnorm * 8.0

    def InputKeyUp(self, keycode):
        if keycode in self.TrackKeys:
            self.KeyStates[keycode] = False
            
        m = self.Pawn.Actor.Movement if self.Pawn else None
        if m:
            if keycode == EKey.ARROW_LEFT and m.Velocity.X < 0.0:
                m.Velocity.X = 0.0
            elif keycode == EKey.ARROW_RIGHT and m.Velocity.X > 0.0:
                m.Velocity.X = 0.0
            elif keycode == EKey.ARROW_DOWN and m.Velocity.Y > 0.0:
                m.Velocity.Y = 0.0
            elif keycode == EKey.ARROW_UP and m.Velocity.Y < 0.0:
                m.Velocity.Y = 0.0

    OnInputKeyDown = InputKeyDown
    OnInputKeyUp = InputKeyUp

    def ResetInput(self):
        '''Reset input state and pawn velocity.'''
        self.KeyStates = {}
        m = self.Pawn.Actor.Movement if self.Pawn else None
        if m:
            m.Velocity = (0.0, 0.0, 0.0)

    def OnViewportActivate(self, viewport, active):
        '''Resets input state when client viewport is deactivated.'''
        if not active:
            self.ResetInput()

@NativeClass
@ComponentClass
class APawn(APawnBase, AComponent):
    PlaceableClass = True
    __varinfo__ = (
        dict(name='Controller', type=AComponent, default=None),
        )

    def __init__(self):
        self.__initnative__()
        AComponent.__init__(self)
        self.MaxHealth = 100.0
        self._Health = 100.0
        self.MaxDamageResist = 0.85
        self.DamageResist = 0.0
        
    def __getstate__(self):
        VERSION = 1
        return (
            VERSION,
            super(APawn, self).__getstate__(),
            self._Controller
            )
    
    def __setstate__(self, state):
        version = state[0]
        super(APawn, self).__setstate__(state[1])
        self._Controller = state[2]
    
    def Exit(self):
        # TODO: Assertion here causes assertion in ANetwork::Exit() due to null
        #       level and actor weak refs for that component
        GLogger.debug('pawn %s currently has controller: %s', self, self._Controller)
        assert not self._Controller
        super(APawn, self).Exit()
    
    def OnGameStopped(self):
        if self._Controller:
            self._Controller.Unpossess()
            assert not self._Controller
        AComponent.OnGameStopped(self)
                
    def PossessedBy(self, controller):
        self._Controller = controller
        self.Actor.Owner = controller.Actor
        
    def Unpossessed(self):
        self._Controller = None
        self.Actor.Owner = None

    @property
    def Health(self):
        return self._Health

    @Health.setter
    def Health(self, value):
        self._Health = Clamp(value, 0.0, self.MaxHealth)

    def ApplyDamage(self, amount, type=None, instigator=None):
        '''Apply damage to the pawn.

        Consults the controller first if possible.
        '''
        assert amount >= 0.0, 'negative damage amount'
        if self.Controller:
            ignore, amount = self.Controller.PawnApplyDamage(amount, type, instigator)
            if ignore:
                return
        amount *= 1.0 - Clamp(self.DamageResist, 0.0, self.MaxDamageResist)
        self.Health -= amount
        if self.Health == 0.0:
            self.Actor.SendMessage('Kill', instigator)

    def HealDamage(self, amount, type=None, instigator=None):
        '''Heal damage done to the pawn if its not dead.'''
        assert amount >= 0.0, 'negative heal amount'
        if self.Health > 0.0:
            self.Health += amount

    def Kill(self, instigator=None):
        '''Kill the pawn. and reduce its health to 0.'''
        if self.Controller:
            ignore = self.Controller.PawnKill(instigator)
            if ignore:
                return
        if self.Health > 0.0:
            self.Health = 0.0
        self.Actor.SendMessage('Killed', instigator)
        GLogger.debug('pawn killed: %s', self)

    def OnKilled(self, instigator):
        if self.Controller:
            self.Controller.PawnKilled(instigator)

    OnApplyDamage = ApplyDamage
    OnHealDamage = HealDamage
    OnKill = Kill
        
class FRepClassInfo(object):
    __slots__ = [
        'Class', # Component class
        'Index', # class.__repindex__
        'ClassIndices', # class.__repclassindices__
        'Vars', # class.__allrepvars__
        'Notifies', # class.__allrepnotifies__
        'Map', # dict of index -> varname and varname -> index
        'Recent', # recent values, len(Vars)
        '_Inst' # component instance
    ]
    Inst = CWeakRefDescr('_Inst')
        
@NativeClass
@ComponentClass
class ANetwork(ANetworkBase, AComponent):
    PlaceableClass = False
    def __init__(self):
        self.__initnative__()
        AComponent.__init__(self)
        #self.VarsList = None
        # map index -> varname, and varname -> index
        #self.VarsDict = None
        #self.ClassList = None
        # map varname -> class
        #self.ClassDict = None
        # repindex -> repclassinfo
        self.RepClassMap = None
        self.RepClasses = None
        self.PickleFunc = cpickle.dumps
        self.PickleProtocol = cpickle.HIGHEST_PROTOCOL
        self.UnpicklerClass = cpickle.Unpickler
        
    __getstate_manages_dict__ = True
    def __getstate__(self):
        VERSION = 2
        return (
            VERSION,
            self.UseNativeRep,
            self.UsePythonRep
            )

    def __setstate__(self, state):
        version = state[0]
        if version == 1:
            self.Actor.Role = state[1]
            self.Actor.RemoteRole = state[2]
            self.UseNativeRep = state[3]
            self.UsePythonRep = state[4]
        else:
            self.UseNativeRep = state[1]
            self.UsePythonRep = state[2]

        # TODO: Proper loading for this 

    def InitPythonRepComp(self, comp):
        assert isinstance(comp, AComponent)
        compcls = type(comp)
        vars = compcls.__allrepvars__
        if len(vars) < 1:
            return False
        index = len(self.RepClasses)
        rci = FRepClassInfo()
        rci.Class = compcls
        #rci.Index = index
        rci.Vars = vars
        rci.Notifies = compcls.__allrepnotifies__
        rci.ClassIndices = compcls.__repclassindices__
        rci.Inst = comp
        rci.Map = map = {}
        for index, varname in enumerate(vars):
            map[varname] = index
            map[index] = varname
        rci.Recent = [None] * len(vars)
        #self.RepClassMap[rci.Index] = rci
        self.RepClasses.append(rci)
        GLogger.debug('Inited Python replication for %s', self)
        #base.GLogger.debug('found component %s:', rci.Class.__name__)
        #base.GLogger.debug('  index = %s', rci.Index)
        GLogger.debug('  vars = %s', rci.Vars)
        GLogger.debug('  notifies = %s', rci.Notifies)
        GLogger.debug('  classindices = %s', rci.ClassIndices)
        return True

    def CleanupPythonRepComp(self, comp):
        for rci in self.RepClasses:
            if rci.Inst is comp:
                #del self.RepClassMap[rci.Index]
                self.RepClasses.remove(rci)
                break

    def InitPythonRep(self):
        '''
        Initialize the metadata needed for Python replication.
        
        This method will iterate through the components of its owner and
        create a FRepClassInfo instance for each component that can
        replicate variables.
        
        self.HasPythonRep is True if initialization was successful.
        '''
        assert self.Actor
        assert not self.HasPythonRep
        
        GLogger.debug('initing python replication for %s ...', self.Actor)
        
        self.RepClasses = []
        self.RepClassMap = {}
        # Iterate through the components in owner.
        for comp in self.Actor:
            self.InitPythonRepComp(comp)
        
        if len(self.RepClasses) > 0:
            self._HasPythonRep = True
        else:
            self._HasPythonRep = False
        
    def ExitPythonRep(self):
        assert self.HasPythonRep
        
        self.RecentPython = None
        self.RepClasses = None
        self.RepClassMap = None
        self._HasPythonRep = False
        
    def ReplicatePython(self, all=False):
        '''
        Replicate Python variables.
        
        This method will iteratate through each FRepClassInfo in
        self.RepClasses and perform replication for each component.
        
        Replication involves calling __getreplist__ to get variable names
        that are candidates for replication, then comparing them to the
        most recently replicated value. If 'all' is True then
        __getreplist__ will not be called and all variables will be tested.
        '''
        assert self.HasPythonRep
        
        out = []
        out_append = out.append
        role = self.Actor.Role
        remoterole = self.Actor.RemoteRole
        for compindex, rci in enumerate(self.RepClasses):
            #inst = self.Actor.GetComponent(rci.Class)
            inst = rci.Inst
            #assert inst
            mro = rci.Class.mro()
            # The function __getreplist__ is called for each class in
            # the mro. It will yield tuples of variable names that
            # are going to be replicated.
            if all:
                varnames = rci.Vars
            else:
                varnames = []
                for index in rci.ClassIndices:
                    for reptuple in mro[index].__getreplist__(inst, role, remoterole):
                        varnames.extend(reptuple)
            
            cout = []
            cout_append = cout.append
            map = rci.Map
            recent = rci.Recent
            for varname in varnames:
                index = map[varname]
                value = getattr(inst, varname)
                # consider using 'is not' here
                if value != recent[index]:
                    cout_append(index)
                    cout_append(value)
                    recent[index] = value
                    
            if len(cout) > 0:

                out_append(compindex)
                out_append(cout)
        
        if len(out) == 0:
            return None
        return self.PickleDumpImpl(out)
        #return out
        
    def UpdatePython(self, data):
        assert type(data) is str
        unpickler = self.UnpicklerClass(cstringio.StringIO(data))
        unpickler.find_global = None
        changes = unpickler.load()
        for i in xrange(0, len(changes), 2):
            compindex = changes[i]
            compdata = changes[i + 1]

            rci = self.RepClasses[compindex]
            comp = rci.Inst
            vmap = rci.Map
            notifies = rci.Notifies
            for e in xrange(0, len(compdata), 2):
                index = compdata[i]
                value = compdata[i + 1]
                vname = vmap[index]
                setattr(comp, vname, value)
                if vname in notifies:
                    comp.OnFieldReplicated(vname)

        
    def PickleDumpImpl(self, data):
        return self.PickleFunc(data, self.PickleProtocol)

@ComponentClass
class AGameInfo(AComponent):
    def __init__(self):
        AComponent.__init__(self)
        self.GameClass = None

def GetItemDefault(obj, index, default=None):
    try:
        return obj[index]
    except Exception:
        return default

@ComponentClass
class ALevelLink(AComponent):
    '''A link between levels that instigates a level change when touched.'''
    __varinfo__ = (
        dict(name='DestLevelName', type=unicode, default=None),
        dict(name='DestPosition', type=FVector3F, definit=True)
        )

    def __init__(self):
        AComponent.__init__(self)
        self.DestLevelName = None
        self.DestPosition = FVector3F()

    def __getstate__(self):
        VERSION = 1
        return (
            VERSION,
            AComponent.__getstate__(self),
            self.DestLevelName,
            self.DestPosition
            )

    def __setstate__(self, state):
        version = state[0]
        AComponent.__setstate__(self, state[1])
        self.DestLevelName = state[2]
        self.DestPosition = state[3]

    def OnBeganTouch(self, other):
        self.Level.Game.OnTouchedLevelLink(self, other.Actor)

@NativeClass
class CActor(CActorBase, CObject):
    '''Actor class representing objects in levels.

    This class serves as a container for components.
    '''
    UnpickleLevel = None
    
    def __init__(self):
        self.__initnative__()
        CObject.__init__(self)
        self.IsTransient = False
        self.TestResource = None

    @property
    def IDStr(self):
        '''A simple <ID>:<Name> string for identifying this actor.'''
        if self.Name:
            # TODO: If self.ID is not converter into string, exception
            #       causes assertion with Python garbage collector.
            #       Investigate this sometime.
            return ''.join((str(self.ID), ':', str(self.Name)))
        else:
            return str(self.ID)
        
    def __repr__(self):
        return '<actor {} in {}>'.format(self.IDStr, self.Level.Name)
    
    def __getstate__(self):
        VERSION = 2
        # 2 - add networking vars
        return (
            VERSION,
            CActorBase.__getstate__(self),
            self.IsTransient,
            self.TestResource,
            self.Role,
            self.RemoteRole,
            self.AlwaysRelevant,
            self.OnlyRelevantToOwner
            )
            
    def __setstate__(self, state):
        version = state[0]
        CActorBase.__setstate__(self, state[1])
        self.IsTransient = state[2]
        self.TestResource = state[3]
        if version >= 2:
            self.Role = state[4]
            self.RemoteRole = state[5]
            self.AlwaysRelevant = state[6]
            self.OnlyRelevantToOwner = state[7]

    def __reduce_ex__(self, protocol):
        assert protocol == 2
        return (type(self), (), self.__getstate__())

    def IterComponents(self):
        '''Iterate through this actor's components.'''
        for i in xrange(self.ComponentCount):
            yield self.GetComponent(i)

    def IterChildren(self):
        '''Iterate through actors owned by this actor.'''
        for i in xrange(self.ChildCount):
            child = self.GetChild(i)
            assert child
            yield child
    
    def SendMessageA(self, name, args=(), kwds=None):
        self.SendMessage(name, *args, **kwds)

    def SendMessage(self, name, *args, **kwds):
        '''Send a message to all components of this actor.

        Message sending is implemented by 
        '''
        fname = 'On' + name
        for comp in self.IterComponents():
            m = getattr(comp, fname, None)
            if m:
                #assert callable(m), 'message handler must be callable'
                m(*args, **kwds)